2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #import "KNAppDelegate.h"
26 #import "KDSecCircle.h"
27 #import "KDCirclePeer.h"
28 #import "NSDictionary+compactDescription.h"
29 #import <AOSUI/NSImageAdditions.h>
30 #import <AppleSystemInfo/AppleSystemInfo.h>
31 #import <Security/SecFrameworkStrings.h>
33 #import <utilities/debugging.h>
34 #import <utilities/SecCFWrappers.h>
35 #import <utilities/SecXPCError.h>
36 #import <os/variant_private.h>
38 #import <Accounts/Accounts.h>
39 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
40 #import <AOSAccounts/MobileMePrefsCore.h>
41 #import <AOSAccounts/ACAccountStore+iCloudAccount.h>
42 #import <AOSAccounts/iCloudAccount.h>
44 #include <msgtracer_client.h>
45 #include <msgtracer_keys.h>
46 #include <CrashReporterSupport/CrashReporterSupportPrivate.h>
47 #import <ProtectedCloudStorage/CloudIdentity.h>
48 #import "CoreCDP/CDPFollowUpController.h"
49 #import "CoreCDP/CDPFollowUpContext.h"
50 #import <CoreCDP/CDPAccount.h>
52 static const char * const kLaunchLaterXPCName = "com.apple.security.Keychain-Circle-Notification-TICK";
53 static const NSString * const kKickedOutKey = @"KickedOut";
54 static const NSString * const kValidOnlyOutOfCircleKey = @"ValidOnlyOutOfCircle";
55 static const NSString * const kPasswordChangedOrTrustedDeviceChanged = @"TDorPasswordChanged";
56 static NSString *prefpane = @"/System/Library/PreferencePanes/iCloudPref.prefPane";
57 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
58 #define kPublicKeyAvailable "com.apple.security.publickeyavailable"
59 static NSString *KeychainPCDetailsAEAction = @"AKPCDetailsAEAction";
60 bool _hasPostedAndStillInError = false;
61 bool _haveCheckedForICDPStatusOnceInCircle = false;
62 bool _isAccountICDP = false;
64 @implementation KNAppDelegate
66 static NSUserNotificationCenter *appropriateNotificationCenter()
68 return [NSUserNotificationCenter _centerForIdentifier: @"com.apple.security.keychain-circle-notification"
69 type: _NSUserNotificationCenterTypeSystem];
72 static BOOL isErrorFromXPC(CFErrorRef error)
74 // Error due to XPC failure does not provide information about the circle.
75 if (error && (CFEqual(sSecXPCErrorDomain, CFErrorGetDomain(error)))) {
81 static void PSKeychainSyncIsUsingICDP(void)
83 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
84 ACAccount *primaryiCloudAccount = nil;
86 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
87 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
90 NSString *dsid = primaryiCloudAccount.icaPersonID;
91 BOOL isICDPEnabled = NO;
93 isICDPEnabled = [CDPAccount isICDPEnabledForDSID:dsid];
94 NSLog(@"iCDP: PSKeychainSyncIsUsingICDP returning %@", isICDPEnabled ? @"TRUE" : @"FALSE");
96 NSLog(@"iCDP: no primary account");
99 _isAccountICDP = isICDPEnabled;
102 -(void) startFollowupKitRepair
104 NSError *localError = NULL;
105 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
106 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
107 [cdpd postFollowUpWithContext:context error:&localError ];
109 secnotice("kcn", "request to CoreCDP to follow up failed: %@", localError);
112 secnotice("kcn", "CoreCDP handling follow up");
113 _hasPostedAndStillInError = false;
117 - (void) handleDismissedNotification
120 secnotice("kcn", "handling dismissed notification, would start a follow up");
121 [self startFollowupKitRepair];
124 secerror("unable to find primary account");
127 - (void) notifyiCloudPreferencesAbout: (NSString *) eventName
129 if (eventName == nil)
132 secnotice("kcn", "notifyiCloudPreferencesAbout %@", eventName);
134 NSString *accountID = (__bridge_transfer NSString*)(MMCopyLoggedInAccountFromAccounts());
135 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
136 ACAccount *primaryiCloudAccount = nil;
138 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
139 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
142 if(primaryiCloudAccount){
144 BOOL createdAEDesc = createAEDescWithAEActionAndAccountID((__bridge NSString *) kMMServiceIDKeychainSync, eventName, accountID, &aeDesc);
146 NSArray *prefPaneURL = [NSArray arrayWithObject: [NSURL fileURLWithPath: prefpane ]];
148 LSLaunchURLSpec lsSpec = {
150 .itemURLs = (__bridge CFArrayRef)prefPaneURL,
151 .passThruParams = &aeDesc,
152 .launchFlags = kLSLaunchDefaults | kLSLaunchAsync,
156 OSErr err = LSOpenFromURLSpec(&lsSpec, NULL);
159 secerror("Can't send event %@, err=%d", eventName, err);
160 AEDisposeDesc(&aeDesc);
162 secerror("unable to create and send aedesc for account: '%@' and action: '%@'\n", primaryiCloudAccount, eventName);
165 secerror("unable to find primary account");
170 NSDate *nowish = [NSDate new];
172 self.state = [KNPersistentState loadFromStorage];
173 if ([nowish compare:self.state.pendingApplicationReminder] != NSOrderedAscending) {
174 secnotice("kcn", "REMINDER TIME: %@ >>> %@", nowish, self.state.pendingApplicationReminder);
176 // self.circle.rawStatus might not be valid yet
177 if (SOSCCThisDeviceIsInCircle(NULL) == kSOSCCRequestPending) {
178 // Still have a request pending, send reminder, and also in addtion to the UI
179 // we need to send a notification for iCloud pref pane to pick up
180 CFNotificationCenterPostNotificationWithOptions(
181 CFNotificationCenterGetDistributedCenter(),
182 CFSTR("com.apple.security.secureobjectsync.pendingApplicationReminder"),
183 (__bridge const void *) [self.state.applicationDate description], NULL, 0
186 [self postApplicationReminder];
187 self.state.pendingApplicationReminder = [nowish dateByAddingTimeInterval:[self getPendingApplicationReminderInterval]];
188 [self.state writeToStorage];
194 - (void) scheduleActivityAt: (NSDate *) time
196 if ([time compare:[NSDate distantFuture]] != NSOrderedSame) {
197 NSTimeInterval howSoon = [time timeIntervalSinceNow];
199 [self scheduleActivityIn:ceil(howSoon)];
206 - (void) scheduleActivityIn: (int) alertInterval
208 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
209 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
210 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
211 xpc_dictionary_set_bool (options, XPC_ACTIVITY_REPEATING, false);
212 xpc_dictionary_set_bool (options, XPC_ACTIVITY_ALLOW_BATTERY, true);
213 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
215 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
221 - (NSTimeInterval) getPendingApplicationReminderInterval
223 if (self.state.pendingApplicationReminderInterval)
224 return [self.state.pendingApplicationReminderInterval doubleValue];
229 - (NSMutableSet *) makeApplicantSet {
230 KNAppDelegate *me = self;
231 NSMutableSet *applicantIds = [NSMutableSet new];
232 for (KDCirclePeer *applicant in me.circle.applicants) {
233 [me postForApplicant:applicant];
234 [applicantIds addObject:applicant.idString];
239 - (bool) removeAllNotificationsOfType: (NSString *) typeString {
240 bool didRemove = false;
241 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
242 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
243 if (note.userInfo[typeString]) {
244 [appropriateNotificationCenter() removeDeliveredNotification: note];
251 static const char *sosCCStatusCString(SOSCCStatus status) {
253 case kSOSCCError: return "kSOSCCError";
254 case kSOSCCInCircle: return "kSOSCCInCircle";
255 case kSOSCCNotInCircle: return "kSOSCCNotInCircle";
256 case kSOSCCCircleAbsent: return "kSOSCCCircleAbsent";
257 case kSOSCCRequestPending: return "kSOSCCRequestPending";
258 default: return "unknown";
262 static const char *sosDepartureReasonCString(enum DepartureReason departureReason){
263 switch(departureReason) {
264 case kSOSDepartureReasonError: return "kSOSDepartureReasonError";
265 case kSOSNeverLeftCircle: return "kSOSNeverLeftCircle";
266 case kSOSWithdrewMembership: return "kSOSWithdrewMembership";
267 case kSOSMembershipRevoked: return "kSOSMembershipRevoked";
268 case kSOSLeftUntrustedCircle: return "kSOSLeftUntrustedCircle";
269 case kSOSNeverAppliedToCircle: return "kSOSNeverAppliedToCircle";
270 case kSOSDiscoveredRetirement: return "kSOSDiscoveredRetirement";
271 case kSOSLostPrivateKey: return "kSOSLostPrivateKey";
272 default: return "unknown reason";
277 - (void) processCircleState {
278 CFErrorRef err = NULL;
279 KNAppDelegate *me = self;
281 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&err);
282 if (isErrorFromXPC(err)) {
283 secnotice("kcn", "SOSCCGetLastDepartureReason failed with xpc error: %@", err);
287 secnotice("kcn", "SOSCCGetLastDepartureReason failed with: %@", err);
291 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&err);
292 if (isErrorFromXPC(err)) {
293 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with xpc error: %@", err);
297 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with: %@", err);
301 NSDate *nowish = [NSDate date];
302 me.state = [KNPersistentState loadFromStorage];
303 SOSCCStatus lastCircleStatus = me.state.lastCircleStatus;
305 PSKeychainSyncIsUsingICDP();
307 secnotice("kcn", "processCircleState starting ICDP: %s SOSCCStatus: %s DepartureReason: %s",
308 (_isAccountICDP) ? "Enabled": "Disabled",
309 sosCCStatusCString(circleStatus),
310 sosDepartureReasonCString(departureReason));
313 me.state.lastCircleStatus = circleStatus;
314 [me.state writeToStorage];
316 switch(circleStatus) {
318 secnotice("kcn", "iCDP: device is in circle!");
319 _hasPostedAndStillInError = false;
321 case kSOSCCRequestPending:
322 [me scheduleActivityAt:me.state.pendingApplicationReminder];
324 case kSOSCCCircleAbsent:
325 case kSOSCCNotInCircle:
326 [me outOfCircleAlert: departureReason];
327 secnotice("kcn", "{ChangeCallback} Pending request START");
328 me.state.applicationDate = nowish;
329 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
330 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
331 [me scheduleActivityAt:me.state.pendingApplicationReminder];
335 You would think we could count on not being iCDP if the account was signed out. Evidently that's wrong.
336 So we'll go based on the artifact that when the account object is reset (like by signing out) the
337 departureReason will be set to kSOSDepartureReasonError. So we won't push to get back into a circle if that's
338 the current reason. I've checked code for other ways we could be out. If we boot and can't load the account
339 we'll end up with kSOSDepartureReasonError. Then too if we end up in kSOSDepartureReasonError and reboot we end up
340 in the same place. Leave it to cdpd to decide whether the user needs to sign in to an account.
342 if(departureReason != kSOSDepartureReasonError) {
343 secnotice("kcn", "ICDP: We need the password to initiate trust");
344 [me postRequirePassword];
345 _hasPostedAndStillInError = true;
347 secnotice("kcn", "iCDP: We appear to not be associated with an iCloud account");
351 secnotice("kcn", "Bad SOSCCStatus return %d", circleStatus);
354 } else { // SA version
355 switch(circleStatus) {
357 secnotice("kcn", "SA: device is in circle!");
358 _hasPostedAndStillInError = false;
360 case kSOSCCRequestPending:
361 [me scheduleActivityAt:me.state.pendingApplicationReminder];
362 secnotice("kcn", "{ChangeCallback} scheduleActivity %@", me.state.pendingApplicationReminder);
364 case kSOSCCCircleAbsent:
365 case kSOSCCNotInCircle:
366 switch (departureReason) {
367 case kSOSDiscoveredRetirement:
368 case kSOSLostPrivateKey:
369 case kSOSWithdrewMembership:
370 case kSOSNeverAppliedToCircle:
371 case kSOSDepartureReasonError:
372 case kSOSMembershipRevoked:
374 if(me.state.lastCircleStatus == kSOSCCInCircle) {
375 secnotice("kcn", "SA: circle status went from in circle to %s: reason: %s", sosCCStatusCString(circleStatus), sosDepartureReasonCString(departureReason));
379 case kSOSNeverLeftCircle:
380 case kSOSLeftUntrustedCircle:
381 [me outOfCircleAlert: departureReason];
382 secnotice("kcn", "{ChangeCallback} Pending request START");
383 me.state.applicationDate = nowish;
384 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
385 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
386 [me scheduleActivityAt:me.state.pendingApplicationReminder];
391 if(me.state.lastCircleStatus == kSOSCCInCircle && (departureReason == kSOSNeverLeftCircle)) {
392 secnotice("kcn", "SA: circle status went from in circle to error - we need the password");
393 [me postRequirePassword];
394 _hasPostedAndStillInError = true;
398 secnotice("kcn", "Bad SOSCCStatus return %d", circleStatus);
404 // Circle applications: pending request(s) started / completed
405 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
406 secnotice("kcn", "Pending request completed");
407 me.state.applicationDate = [NSDate distantPast];
408 me.state.pendingApplicationReminder = [NSDate distantFuture];
409 [me.state writeToStorage];
412 if([me removeAllNotificationsOfType: @"ApplicationReminder"]) {
413 secnotice("kcn", "{ChangeCallback} removed application remoinders");
417 // Clear out (old) reset notifications
418 if (circleStatus == kSOSCCInCircle) {
419 secnotice("kcn", "{ChangeCallback} kSOSCCInCircle");
420 if([me removeAllNotificationsOfType: (NSString*) kValidOnlyOutOfCircleKey]) {
421 secnotice("kcn", "Removed existing notifications now that we're in circle");
423 if([me removeAllNotificationsOfType: (NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
424 secnotice("kcn", "Removed existing password notifications now that we're in circle");
428 secnotice("kcn", "{ChangeCallback} Applicants");
429 NSMutableSet *applicantIds = [me makeApplicantSet];
430 // Clear applicant notifications that aren't pending any more
431 NSUserNotificationCenter *notificationCenter = appropriateNotificationCenter();
432 secnotice("kcn", "Checking validity of %lu notes", (unsigned long)notificationCenter.deliveredNotifications.count);
433 for (NSUserNotification *note in notificationCenter.deliveredNotifications) {
434 if (note.userInfo[@"applicantId"] && ![applicantIds containsObject:note.userInfo[@"applicantId"]]) {
435 secnotice("kcn", "No longer an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
436 [notificationCenter removeDeliveredNotification:note];
438 secnotice("kcn", "Still an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
441 } else { // Clear any pending applicant notifications since we aren't in circle or invalid
442 if([me removeAllNotificationsOfType: (NSString*) @"applicantId"]) {
443 secnotice("kcn", "Not in circle or invalid - removed applicant notes");
447 me.state.lastCircleStatus = circleStatus;
448 [me.state writeToStorage];
451 - (void) applicationDidFinishLaunching: (NSNotification *) aNotification
453 appropriateNotificationCenter().delegate = self;
456 secnotice("kcn", "Posted at launch: %@", appropriateNotificationCenter().deliveredNotifications);
458 notify_register_dispatch(kPublicKeyAvailable, &available, dispatch_get_main_queue(), ^(int token) {
459 CFErrorRef err = NULL;
460 KNAppDelegate *me = self;
461 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&err);
463 if (isErrorFromXPC(err)) {
464 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with: %@", err);
470 me.state = [KNPersistentState loadFromStorage];
472 secnotice("kcn", "got public key available notification");
473 me.state.lastCircleStatus = currentCircleStatus;
474 [me.state writeToStorage];
477 //register for public key not available notification, if occurs KCN can react
478 notify_register_dispatch(kPublicKeyNotAvailable, &out_taken, dispatch_get_main_queue(), ^(int token) {
479 secnotice("kcn", "got public key not available notification");
480 KNAppDelegate *me = self;
481 [me processCircleState];
484 self.viewedIds = [NSMutableSet new];
485 self.circle = [KDSecCircle new];
486 KNAppDelegate *me = self;
488 [self.circle addChangeCallback:^{
489 secnotice("kcn", "{ChangeCallback}");
490 [me processCircleState];
495 - (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center
496 shouldPresentNotification: (NSUserNotification *) notification
502 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
503 didActivateNotification: (NSUserNotification *) notification
505 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
506 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Activate"]];
511 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
512 didDismissAlert: (NSUserNotification *) notification
514 [self handleDismissedNotification];
516 // If we don't do anything here & another notification comes in we
517 // will repost the alert, which will be dumb.
518 id applicantId = notification.userInfo[@"applicantId"];
519 if (applicantId != nil) {
520 [self.viewedIds addObject:applicantId];
525 - (void) postForApplicant: (KDCirclePeer *) applicant
527 static int postCount = 0;
529 if ([self.viewedIds containsObject:applicant.idString]) {
530 secnotice("kcn", "Already viewed %@, skipping", applicant);
534 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
535 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
536 if ([applicant.idString isEqualToString:note.userInfo[@"applicantId"]]) {
537 if (note.isPresented) {
538 secnotice("kcn", "Already posted&presented: %@ (I=%@)", note, note.userInfo);
541 secnotice("kcn", "Already posted, but not presented: %@ (I=%@)", note, note.userInfo);
546 NSUserNotification *note = [NSUserNotification new];
547 note.title = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE), applicant.name];
548 note.informativeText = [KNAppDelegate localisedApprovalBodyWithDeviceTypeFromPeerInfo:applicant.peerObject];
549 note._displayStyle = _NSUserNotificationDisplayStyleAlert;
550 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
551 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
552 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_DECLINE);
553 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVE);
554 note.identifier = [[NSUUID new] UUIDString];
556 @"applicantName": applicant.name,
557 @"applicantId" : applicant.idString,
558 @"Activate" : (__bridge NSString *) kMMPropertyKeychainAADetailsAEAction,
561 secnotice("kcn", "About to post #%d/%lu (%@): %@", postCount, noteCenter.deliveredNotifications.count, applicant.idString, note);
562 [appropriateNotificationCenter() deliverNotification:note];
563 [self.viewedIds addObject:applicant.idString];
567 + (NSString *)localisedApprovalBodyWithDeviceTypeFromPeerInfo:(id)peerInfo {
568 NSString *type = (__bridge NSString *)SOSPeerInfoGetPeerDeviceType((__bridge SOSPeerInfoRef)(peerInfo));
569 CFStringRef localisedType = NULL;
570 if ([type isEqualToString:@"iPad"]) {
571 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPAD);
572 } else if ([type isEqualToString:@"iPhone"]) {
573 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPHONE);
574 } else if ([type isEqualToString:@"iPod"]) {
575 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPOD);
576 } else if ([type isEqualToString:@"Mac"]) {
577 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_MAC);
579 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_GENERIC);
581 return (__bridge_transfer NSString *)localisedType;
584 - (void) postRequirePassword
586 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(NULL);
587 if(currentCircleStatus != kSOSCCError) {
588 secnotice("kcn", "postRequirePassword when not needed");
592 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(NULL);
595 secnotice("kcn","would have posted needs password and then followed up");
596 [self startFollowupKitRepair];
597 } else if(departureReason == kSOSNeverLeftCircle) { // The only SA case for prompting
598 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
599 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
600 if (note.userInfo[(NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
601 if (note.isPresented) {
602 secnotice("kcn", "Already posted & presented: %@", note);
603 [appropriateNotificationCenter() removeDeliveredNotification: note];
605 secnotice("kcn", "Already posted, but not presented: %@", note);
610 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
611 if (os_variant_has_internal_ui("iCloudKeychain")) {
612 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), "Device became untrusted or password changed"];
613 message = [message stringByAppendingString: reason_str];
616 NSUserNotification *note = [NSUserNotification new];
617 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
618 note.informativeText = message;
619 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
620 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
621 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
622 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
623 note.identifier = [[NSUUID new] UUIDString];
626 kPasswordChangedOrTrustedDeviceChanged : @1,
627 @"Activate" : (__bridge NSString *) kMMPropertyKeychainPCDetailsAEAction,
630 secnotice("kcn", "body=%@", note.informativeText);
631 secnotice("kcn", "About to post #-/%lu (PASSWORD/TRUSTED DEVICE): %@", noteCenter.deliveredNotifications.count, note);
632 [appropriateNotificationCenter() deliverNotification:note];
634 secnotice("kcn", "postRequirePassword when not needed for SA");
638 - (void) outOfCircleAlert: (int) reason
642 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
643 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
644 if (note.userInfo[(NSString*) kKickedOutKey]) {
645 if (note.isPresented) {
646 secnotice("kcn", "Already posted&presented (removing): %@", note);
647 [appropriateNotificationCenter() removeDeliveredNotification: note];
649 secnotice("kcn", "Already posted, but not presented: %@", note);
654 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
655 if (os_variant_has_internal_ui("iCloudKeychain")) {
656 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), sosDepartureReasonCString(reason)];
657 message = [message stringByAppendingString: reason_str];
660 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
661 // Contrary to HI spec (and I think it makes more sense)
662 // 1. otherButton == top : Not Now
663 // 2. actionButton == bottom: Continue
664 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
665 NSUserNotification *note = [NSUserNotification new];
666 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
667 note.informativeText = message;
668 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
669 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
670 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
671 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
672 note.identifier = [[NSUUID new] UUIDString];
676 kValidOnlyOutOfCircleKey: @1,
677 @"Activate" : (__bridge NSString *) kMMPropertyKeychainMRDetailsAEAction,
680 secnotice("kcn", "body=%@", note.informativeText);
681 secnotice("kcn", "About to post #-/%lu (KICKOUT): %@", noteCenter.deliveredNotifications.count, note);
682 [appropriateNotificationCenter() deliverNotification:note];
686 secnotice("kcn","outOfCircleAlert starting followup repair");
687 [self startFollowupKitRepair];
691 - (void) postApplicationReminder
693 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
694 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
695 if (note.userInfo[@"ApplicationReminder"]) {
696 if (note.isPresented) {
697 secnotice("kcn", "Already posted&presented (removing): %@", note);
698 [appropriateNotificationCenter() removeDeliveredNotification: note];
700 secnotice("kcn", "Already posted, but not presented: %@", note);
705 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
706 // Contrary to HI spec (and I think it makes more sense)
707 // 1. otherButton == top : Not Now
708 // 2. actionButton == bottom: Continue
709 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
710 NSUserNotification *note = [NSUserNotification new];
711 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_TITLE_OSX);
712 note.informativeText = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_BODY_OSX);
713 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
714 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
715 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
716 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
717 note.identifier = [[NSUUID new] UUIDString];
720 @"ApplicationReminder" : @1,
721 kValidOnlyOutOfCircleKey: @1,
722 @"Activate" : (__bridge NSString *) kMMPropertyKeychainWADetailsAEAction,
725 secnotice("kcn", "About to post #-/%lu (REMINDER): %@ (I=%@)", noteCenter.deliveredNotifications.count, note, [note.userInfo compactDescription]);
726 [appropriateNotificationCenter() deliverNotification:note];